﻿// This source file is part of WallpaperControl, a tool for automatic changing 
// of the desktop wallpaper.
// 
// Visit http://wpcontrol.sourceforge.net/ for more.
//
// Copyright (c) 2014 nscrolldev
//
// Licensed under the GPLv2.
// See http://www.gnu.org/licenses/gpl-2.0.html for details.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

using System;
using System.IO;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using Microsoft.Win32;

namespace WallpaperControl
{
    /// <summary>
    /// Manages the windows wallpaper including setting the next, the previous and
    /// the selected wallpaper as desktop background.
    /// </summary>
    sealed class WallpaperManager
    {
        /// <summary>
        /// Possible modes for wallpaper changing.
        /// </summary>
        public enum ChangeOrder
        {
            Ascending,
            Descending,
            Random
        }

        /// <summary>
        /// Holds the only instance of the wallpaper manager.
        /// </summary>
        private static readonly WallpaperManager _instance = new WallpaperManager();

        /// <summary>
        /// The index of the next wallpaper in the list. Starting with index 0 
        /// (first item).
        /// </summary>
        private int _nextWallpaperIndex;

        /// <summary>
        /// The index of the previous wallpaper in the list.
        /// </summary>
        private int _previousWallpaperIndex;

        /// <summary>
        /// The index of the current wallpaper.
        /// </summary>
        private int _currentWallpaperIndex;

        /// <summary>
        /// The order in which the wallpaper is changed.
        /// </summary>
        private ChangeOrder _order;

        /// <summary>
        /// The original order in which the wallpaper should be changed.
        /// </summary>
        private ChangeOrder _oldOrder;

        /// <summary>
        /// Determines whether the change order has been changed.
        /// </summary>
        private bool _orderChanged;

        /// <summary>
        /// Gets or sets the index of the next wallpaper.
        /// </summary>
        internal int NextWallpaperIndex
        {
            get
            {
                return this._nextWallpaperIndex;
            }
            set
            {
                this._nextWallpaperIndex = value;
            }
        }

        /// <summary>
        /// Gets or sets the index of the previous wallpaper.
        /// </summary>
        internal int PreviousWallpaperIndex
        {
            get
            {
                return this._previousWallpaperIndex;
            }
            set
            {
                this._previousWallpaperIndex = value;
            }
        }

        /// <summary>
        /// Gets or sets the index of the current wallpaper.
        /// </summary>
        internal int CurrentWallpaperIndex
        {
            get
            {
                return this._currentWallpaperIndex;
            }
            set
            {
                this._currentWallpaperIndex = value;
            }
        }

        /// <summary>
        /// Gets the order in which the wallpaper is changed.
        /// </summary>
        internal ChangeOrder Order
        {
            get
            {
                return this._order;
            }
            set
            {
                this._order = value;
                this.OrderChanged = true;
            }
        }

        /// <summary>
        /// Gets or sets the original change order.
        /// </summary>
        internal ChangeOrder OldOrder
        {
            get
            {
                return this._oldOrder;
            }
            set
            {
                this._oldOrder = value;
            }
        }

        /// <summary>
        /// Determines whether the order has been changed.
        /// </summary>
        private bool OrderChanged
        {
            get
            {
                return this._orderChanged;
            }
            set
            {
                this._orderChanged = value;
            }
        }

        /// <summary>
        /// Gets the instance of the wallpaper manager.
        /// </summary>
        public static WallpaperManager Instance
        {
            get
            {
                return _instance;
            }
        }

        /// <summary>
        /// Constructor that loads the default values if a new manager instance is 
        /// started.
        /// </summary>
        private WallpaperManager()
        {
            this.NextWallpaperIndex = 0;
            this.CurrentWallpaperIndex = -1;
            this.PreviousWallpaperIndex = -2;
        }

        /// <summary>
        /// Represents that the wallpaper should be changed.
        /// </summary>
        /// <remarks>Used as first argument in SystemParametersInfo()</remarks>
        const int SPI_SETDESKWALLPAPER = 20;
        /// <summary>
        /// Specifies if the INI file should be updated.
        /// </summary>
        const int SPIF_UPDATEINIFILE = 0x01;
        /// <summary>
        /// Specifies if it should be broadcasted that the INI file has been changed.
        /// </summary>
        const int SPIF_SENDWININICHANGE = 0x02;

        /// <summary>
        /// Sets the windows wallpaper.
        /// </summary>
        /// <param name="path">The path to the image file.</param>
        /// <param name="style">The syle how the wallpaper should be displayed if it doesn't fit the screen.</param>
        /// <exception cref="System.IO.FileNotFoundException">Thrown if the specified file is not existing.</exception>
        /// <exception cref="System.ArgumentNullException">Thrown if the param 'path' is null or an empty string.</exception>
        internal void SetWallpaper(string path, Wallpaper.Style style)
        {
            // Make sure the path is set
            if (path == null || path.Length == 0)
            {
                throw new ArgumentNullException("path", "The path to the wallpaper is not set!");
            }
            Image wallpaperImage = new Image(path);
            // Check if windows can handle wallpaper in the specified format.
            if (!wallpaperImage.IsSupported())
            {
                // If this is not the case, convert it to bitmap.
                this.SetWallpaper(new Wallpaper(path, style).Convert(ImageFormat.Bmp));
                return;
            }

            // Check if the specified file exists
            if (File.Exists(path))
            {
                RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);

                // Determin the zoom style of the wallpaper and save it to registry
                switch (style)
                {
                    case Wallpaper.Style.Stretched:
                        key.SetValue(@"WallpaperStyle", "2");
                        key.SetValue(@"TileWallpaper", "0");
                        break;
                    case Wallpaper.Style.Centered:
                        key.SetValue(@"WallpaperStyle", "1");
                        key.SetValue(@"TileWallpaper", "0");
                        break;
                    case Wallpaper.Style.Tiled:
                        key.SetValue(@"WallpaperStyle", "1");
                        key.SetValue(@"TileWallpaper", "1");
                        break;
                }
                // Change wallpaper with SystemParametersInfo from Win API
                SafeNativeMethods.SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
            }
            else
            {
                throw new FileNotFoundException();
            }
        }

        /// <summary>
        /// Sets the windows wallpaper.
        /// </summary>
        /// <param name="wallpaper">The wallpaper object.</param>
        internal void SetWallpaper(Wallpaper wallpaper)
        {
            // Make sure the path is set
            if (wallpaper.Path == null || wallpaper.Path.Length == 0)
            {
                throw new ArgumentNullException("wallpaper", "The path to the wallpaper is invalid.");
            }
            // Check if the file exists
            if (!File.Exists(wallpaper.Path))
            {
                throw new FileNotFoundException("The specified wallpaper does not exist.", wallpaper.Path);
            }
            // Check if windows can handle wallpaper in the specified format.
            if (!wallpaper.Image.IsSupported())
            {
                // If this is not the case, convert it to bitmap.
                wallpaper = wallpaper.Convert(ImageFormat.Bmp);
            }

            // Open the key in which the wallpaper style is stored.
            RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel\\Desktop", true);

            // Determine zoom style and save it to registry.
            switch (wallpaper.ZoomStyle)
            {
                case Wallpaper.Style.Stretched:
                    key.SetValue(@"WallpaperStyle", "2");
                    key.SetValue(@"TileWallpaper", "0");
                    break;
                case Wallpaper.Style.Centered:
                    key.SetValue(@"WallpaperStyle", "1");
                    key.SetValue(@"TileWallpaper", "0");
                    break;
                case Wallpaper.Style.Tiled:
                    key.SetValue(@"WallpaperStyle", "1");
                    key.SetValue(@"TileWallpaper", "1");
                    break;
            }
            
            // Change wallpaper with SystemParametersInfo from Win API
            SafeNativeMethods.SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, wallpaper.Path, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE);
        }

        /// <summary>
        /// Sets the default values so that the next wallpaper is the first image.
        /// </summary>
        internal void SetDefaultValues()
        {
            Program.ControlPanel.DisplayNextWallpaper = false;
            Program.ControlPanel.DisplayCurrentWallpaper = false;
            switch (this.Order)
            {
                case ChangeOrder.Descending:
                    this.NextWallpaperIndex = 0;
                    // Hmm, shouldn't that be NexWallpaperIndex - 2 ??
                    this.PreviousWallpaperIndex = NextWallpaperIndex - 2;
                    this.CurrentWallpaperIndex = NextWallpaperIndex - 1;
                    break;
                case ChangeOrder.Ascending:
                    int lastWallpaper = ImageList.Instance.Images.Count - 1;
                    this.NextWallpaperIndex = lastWallpaper;
                    this.CurrentWallpaperIndex = lastWallpaper + 1;
                    this.PreviousWallpaperIndex = lastWallpaper + 2;
                    break;
                case ChangeOrder.Random:
                    Random randomGen = new Random();
                    this.NextWallpaperIndex = randomGen.Next(0, ImageList.Instance.Images.Count - 1);
                    this.CurrentWallpaperIndex = randomGen.Next(0, ImageList.Instance.Images.Count - 1);
                    this.PreviousWallpaperIndex = randomGen.Next(0, ImageList.Instance.Images.Count - 1);
                    break;
            }

        }

        /// <summary>
        /// Sets the selected wallpaper as background.
        /// </summary>
        private void SetSelectedWallpaper()
        {
            try
            {
                // Get selected image index
                int index = Program.ControlPanel.ListBoxImages.SelectedIndex;
                // Change wallpaper
                
                // Update indices
                this.NextWallpaperIndex = index + 1;
                this.PreviousWallpaperIndex = index - 1;
                this.CurrentWallpaperIndex = index;
            }
            catch (ArgumentNullException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_Argument"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_NotFound"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Sets the wallpaper by its index in the image list
        /// </summary>
        /// <param name="index">The zero-based index of the image in the list of images.</param>
        internal void SetWallpaper(int index)
        {
            try
            {
                // Change wallpaper
                WallpaperManager.Instance.SetWallpaper(ImageList.Instance.Images[index].Path, ImageList.Instance.Images[index].ZoomStyle);
                // Update indices
                this.NextWallpaperIndex = index + 1;
                this.PreviousWallpaperIndex = index - 1;
                this.CurrentWallpaperIndex = index;
            }
            catch (ArgumentNullException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_Argument"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_NotFound"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Sets the previous wallpaper as background.
        /// </summary>
        internal void SetPreviousWallpaper()
        {
            try
            {
                //PreviousWallpaperIndex = NextWallpaperIndex - 2;
                if (PreviousWallpaperIndex >= 0)
                {
                    // Change wallpaper
                    WallpaperManager.Instance.SetWallpaper(ImageList.Instance.Images[PreviousWallpaperIndex].Path, ImageList.Instance.Images[PreviousWallpaperIndex].ZoomStyle);
                    // Keep indices up-to-date
                    this.NextWallpaperIndex--;
                    this.PreviousWallpaperIndex--;
                    this.CurrentWallpaperIndex--;
                }
                /*else
                {
                    // No image with index lower than 0 - select first wallpaper
                    PreviousWallpaperIndex = 0;
                }*/
                // Keep schedule up-to-date
                Program.ControlPanel.UpdateSchedule();
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_NotFound"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (ArgumentNullException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_Argument"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Sets the next wallpaper as background.
        /// </summary>
        internal void SetNextWallpaper()
        {
            try
            {
                switch (this.Order)
                {
                    case ChangeOrder.Descending:
                        // Check if the last wallpaper is reached
                        if (this.NextWallpaperIndex < ImageList.Instance.Images.Count)
                        {
                            // Change wallpaper
                            this.SetWallpaper(ImageList.Instance.Images[this.NextWallpaperIndex]);
                            // Update indices
                            this.NextWallpaperIndex++;
                            this.PreviousWallpaperIndex++;
                            this.CurrentWallpaperIndex++;
                            // Show balloon tip if requested
                            if (Program.Configuration.ChangeInfo)
                            {
                                if (Program.TrayApp.Active)
                                {
                                    Program.TrayApp.TrayIcon.ShowBalloonTip(2000, "WallpaperControl", Program.Lang.GetString("Tip_WallpaperChanged"), ToolTipIcon.Info);
                                }
                            }
                        }
                        // Last wallpaper reached
                        else
                        {
                            // If loop, begin again with first image.
                            if (Program.Configuration.LoopWallpapers)
                            {
                                WallpaperManager.Instance.SetWallpaper(0);
                                // Show balloon tip if requested
                                if (Program.Configuration.LoopingInfo)
                                {
                                    if (Program.TrayApp.Active)
                                    {
                                        Program.TrayApp.TrayIcon.ShowBalloonTip(2000, "WallpaperControl", Program.Lang.GetString("Tip_WallpaperLooping"), ToolTipIcon.Info);
                                    }
                                }
                            }
                            else
                            {
                                // Stop session
                                Program.CurrentSession.Stop();
                            }
                        }
                        break;
                    case ChangeOrder.Ascending:
                        if (this.NextWallpaperIndex >= 0)
                        {
                            // Set next wallpaper
                            this.SetWallpaper(ImageList.Instance.Images[this.NextWallpaperIndex]);
                            // Update indices
                            this.NextWallpaperIndex--;
                            if (this.CurrentWallpaperIndex - 1 < 0)
                            {
                                this.CurrentWallpaperIndex = this.NextWallpaperIndex + 1;

                            }
                            else
                            {
                                this.CurrentWallpaperIndex--;
                            }
                            if (this.PreviousWallpaperIndex - 1 < 0)
                            {
                                this.PreviousWallpaperIndex = this.CurrentWallpaperIndex + 1;
                            }
                            else
                            {
                                this.PreviousWallpaperIndex--;
                            }
                            // Show balloon tip if requested
                            if (Program.Configuration.ChangeInfo)
                            {
                                if (Program.TrayApp.Active)
                                {
                                    Program.TrayApp.TrayIcon.ShowBalloonTip(2000, "WallpaperControl", Program.Lang.GetString("Tip_WallpaperChanged"), ToolTipIcon.Info);
                                }
                            }
                        }
                        else
                        {
                            if (Program.Configuration.LoopWallpapers)
                            {
                                this.NextWallpaperIndex = ImageList.Instance.Images.Count - 1;
                                /*this.CurrentWallpaperIndex--;
                                if (this.CurrentWallpaperIndex < 0)
                                {
                                    int lastImage = ImageList.Instance.Images.Count - 1;
                                    this.CurrentWallpaperIndex = lastImage - (0 - this.CurrentWallpaperIndex);
                                }
                                this.PreviousWallpaperIndex--;
                                if (this.PreviousWallpaperIndex < 0)
                                {
                                    int lastImage = ImageList.Instance.Images.Count - 1;
                                    this.PreviousWallpaperIndex = lastImage - (0 - this.PreviousWallpaperIndex);
                                }*/
                                if (this.CurrentWallpaperIndex - 1 < 0)
                                {
                                    this.CurrentWallpaperIndex = this.NextWallpaperIndex + 1;
                                    
                                }
                                else
                                {
                                    this.CurrentWallpaperIndex--;
                                }
                                if (this.PreviousWallpaperIndex - 1 < 0)
                                {
                                    this.PreviousWallpaperIndex = this.CurrentWallpaperIndex + 1;
                                }
                                else
                                {
                                    this.PreviousWallpaperIndex--;
                                }
                                this.SetWallpaper(ImageList.Instance.Images[this.NextWallpaperIndex]);
                                // Show balloon tip if requested
                                if (Program.Configuration.LoopingInfo)
                                {
                                    if (Program.TrayApp.Active)
                                    {
                                        Program.TrayApp.TrayIcon.ShowBalloonTip(2000, "WallpaperControl", Program.Lang.GetString("Tip_WallpaperLooping"), ToolTipIcon.Info);
                                    }
                                }
                            }
                            else
                            {
                                Program.CurrentSession.Stop();
                            }
                        }
                        break;
                    case ChangeOrder.Random:
                        if (this.NextWallpaperIndex < ImageList.Instance.Images.Count)
                        {
                            // Set next wallpaper
                            this.SetWallpaper(ImageList.Instance.Images[this.NextWallpaperIndex].Path, ImageList.Instance.Images[this.NextWallpaperIndex].ZoomStyle);
                            // Create random number generator
                            Random randomGen = new Random();
                            // Update indices
                            this.PreviousWallpaperIndex = this.CurrentWallpaperIndex;
                            this.CurrentWallpaperIndex = this.NextWallpaperIndex;
                            bool newWallpaper = true;
                            while (newWallpaper)
                            {
                                int newIndex = randomGen.Next(0, ImageList.Instance.Images.Count);
                                if (newIndex != this.CurrentWallpaperIndex)
                                {
                                    this.NextWallpaperIndex = newIndex;
                                    newWallpaper = false;
                                }
                            }
                            // Show balloon tip if requested
                            if (Program.Configuration.ChangeInfo)
                            {
                                if (Program.TrayApp.Active)
                                {
                                    Program.TrayApp.TrayIcon.ShowBalloonTip(2000, "WallpaperControl", Program.Lang.GetString("Tip_WallpaperChanged"), ToolTipIcon.Info);
                                }
                            }
                        }
                        break;
            }
                // Keep schedule up-to-date
                Program.ControlPanel.DisplayCurrentWallpaper = true;
                Program.ControlPanel.DisplayNextWallpaper = true;
                Program.ControlPanel.UpdateSchedule();
            }
            catch (FileNotFoundException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_NotFound"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (ArgumentNullException)
            {
                MessageBox.Show(Program.Lang.GetString("Error_SetWallpaper_Argument"), Program.Lang.GetString("Caption_Error"), MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Restores the original change order.
        /// </summary>
        internal void RestoreOrder()
        {
            // Check that original change order is available
            if (this.OldOrder != 0)
            {
                // Set current order to unchanged order
                this.Order = this.OldOrder;
                // Mark order as unchanged
                this.OrderChanged = false;
            }
        }
    }
}
